package com.springcloudalibaba.baseredis.demo;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @描述: 分布式锁
 * @作者: lixing
 * @日期 2021/7/10 17:19
 */
@Slf4j
public class ReentrantLockDemo {
    public static RedissonClient redissonClient;

    static {
        Config config = new Config();
        // Redis单节点模式
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setPassword("LI1234REDIS!");
        singleServerConfig.setAddress("redis://192.168.213.137:6379");
        singleServerConfig.setDatabase(0);
        // 客户端实例
        redissonClient = Redisson.create(config);
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(
                10,
                100,
                30, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());
        /*
         * 幂等：
         * 1、通过分布式锁过滤掉短时间内（假设当前业务执行耗时N秒，则N秒内的重复请求都会被过滤掉，但N秒之后的重复请求并不会被过滤）的重复请求
         * 2、添加数据时，设置主键、联合主键约束，防止重复数据的产生
         */
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    RLock rLock = redissonClient.getLock("rLock");
                    boolean res = false;
                    try {
                        /*
                         * 1、如果需要使用续锁功能，请不要设置锁的过期时间（可以设置成-1），否则RedissonLock就会认为你需要自己控制锁时间而放弃执行续锁逻辑
                         * 2、RedissonLock的续锁逻辑是用netty下的Timeout定时器来实现的，默认锁时间是30秒，定时器每锁时间/3也就是10秒执行一次。来判断是否还持有锁，如果还持有锁就重置锁时间为30秒。从而避免业务执行时间大于锁时间而导致的锁被释放了的问题
                         */
                        // 尝试取锁：
                        // 1、取锁超时时间 500 毫秒，超过则取锁失败
                        // 2、取锁成功后，-1表示锁使用默认的有效期30秒（如果业务提前执行完毕也会提前释放锁），每隔30/3（10）秒会自动检测锁是否存在，存在则重置锁的有效期为 30 秒
                        res = rLock.tryLock(500, -1, TimeUnit.MILLISECONDS);
                        if (res) {
                            log.info(Thread.currentThread().getName()+" 拿到锁，开始执行任务");
                            Thread.sleep(8000);
                            log.info(Thread.currentThread().getName()+" 拿到锁，执行任务成功");
                        } else {
                            log.info(Thread.currentThread().getName()+" 超时，未拿到锁");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if(rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                            // 释放锁
                            rLock.unlock();
                        }
                    }
                }
            });
        }
        // 关闭线程池
        executorService.shutdown();
    }
}
